﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization;
using Flower.Services.Data;

namespace Flower.Directory.Util
{
    public static class BlobUtil
    {
        private const int INITIAL_CAPACITY = 256;

        private static XmlWriter CreateWriter(BlobFormat format, Stream stream)
        {
            switch (format)
            {
                case BlobFormat.TextXml:
                    return XmlDictionaryWriter.CreateTextWriter(stream, Encoding.UTF8, true);
                case BlobFormat.BinXml:
                    return XmlDictionaryWriter.CreateBinaryWriter(stream, null, null, true);
                case BlobFormat.GZipXml:
                    return XmlDictionaryWriter.CreateTextWriter(new GZipStream(stream, CompressionMode.Compress, false), Encoding.UTF8, true);
            }

            throw new ArgumentException(string.Format("Unknown BLOB format {0}", format), "format");
        }

        private static XmlReader CreateReader(BlobFormat format, Stream stream)
        {
            switch (format)
            {
                case BlobFormat.TextXml:
                    return XmlDictionaryReader.CreateTextReader(stream, XmlDictionaryReaderQuotas.Max);
                case BlobFormat.BinXml:
                    return XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max);
                case BlobFormat.GZipXml:
                    return XmlDictionaryReader.CreateTextReader(new GZipStream(stream, CompressionMode.Decompress), Encoding.UTF8, XmlDictionaryReaderQuotas.Max, r => { });
            }

            throw new ArgumentException(string.Format("Unknown BLOB format {0}", format), "format");
        }

        /// <summary>
        /// Writes a BLOB using XmlWriter.
        /// </summary>
        /// <param name="format">Format of the BLOB.</param>
        /// <param name="write">The callback writing an XML.</param>
        /// <returns>BLOB with the data written.</returns>
        public static Blob WriteBlob(BlobFormat format, Action<XmlWriter> write)
        {
            var stream = new MemoryStream(INITIAL_CAPACITY);

            using (XmlWriter writer = CreateWriter(format, stream))
            {
                write(writer);
                writer.Flush();
            }

            return new Blob
            {
                Format = format,
                Data = stream.ToArray()
            };
        }

        /// <summary>
        /// Reads a BLOB by using XmlReader.
        /// </summary>
        /// <param name="blob">The BLOB to read.</param>
        /// <param name="read">The callback reading the data.</param>
        /// <returns>The value returned by the callback.</returns>
        public static T ReadBlob<T>(Blob blob, Func<XmlReader, T> read)
        {
            using (XmlReader reader = CreateReader(blob.Format, new MemoryStream(blob.Data)))
            {
                return read(reader);
            }
        }

        /// <summary>
        /// Reads a BLOB by using XmlReader.
        /// </summary>
        /// <param name="blob">The BLOB to read.</param>
        /// <param name="read">The callback reading the data.</param>
        public static void ReadBlob(Blob blob, Action<XmlReader> read)
        {
            using (XmlReader reader = CreateReader(blob.Format, new MemoryStream(blob.Data)))
            {
                read(reader);
            }
        }

        /// <summary>
        /// Serializes an object to a BLOB by using DataContract serializer.
        /// </summary>
        /// <typeparam name="T">The type of 'this'.</typeparam>
        /// <param name="obj">The object to serialize.</param>
        /// <param name="format">Format of the BLOB.</param>
        /// <param name="useRealType">
        /// If true, the type for DataContractSerializer is obtained using GetType; otherwise, T is used.
        /// </param>
        /// <returns>The BLOB with the object serialized.</returns>
        public static Blob ToBlob<T>(this T obj, BlobFormat format, bool useRealType = false)
        {
            return WriteBlob
            (
                format, 
                writer => 
                    new DataContractSerializer((useRealType) ? obj.GetType() : typeof(T))
                        .WriteObject(writer, obj)
            );
        }

        public static string ToReadableXmlString<T>(this T obj)
        {
            var sb = new StringBuilder();
            
            var settings = new XmlWriterSettings 
            {
                Encoding = Encoding.UTF8, 
                Indent = true
            };

            using (var writer = XmlWriter.Create(new StringWriter(sb), settings))
            {
                new DataContractSerializer(typeof(T))
                        .WriteObject(writer, obj);
                writer.Flush();
            }

            return sb.ToString();
        }

        public static void WriteAsReadableXml<T>(this T obj, Stream stream)
        {
            var settings = new XmlWriterSettings
            {
                Encoding = Encoding.UTF8,
                Indent = true,
                CloseOutput = false
            };

            using (var writer = XmlWriter.Create(stream, settings))
            {
                new DataContractSerializer(typeof(T))
                        .WriteObject(writer, obj);
                writer.Flush();
            }
        }

        public static void WriteAsReadableXml(this Blob blob, Stream stream)
        {
            var settings = new XmlWriterSettings
            {
                Encoding = Encoding.UTF8,
                Indent = true,
                CloseOutput = false
            };

            var xmlDoc = new XmlDocument();
            using (var reader = CreateReader(blob.Format, new MemoryStream(blob.Data)))
            {
                xmlDoc.Load(reader);
            }

            using (var writer = XmlWriter.Create(stream, settings))
            {
                xmlDoc.WriteTo(writer);
                writer.Flush();
            }
        }

        /// <summary>
        /// Clones an object through serialization.
        /// </summary>
        /// <typeparam name="T">The type of 'this'.</typeparam>
        /// <param name="obj">The object to clone.</param>
        /// <returns>Copy of the object.</returns>
        public static T Clone<T>(this T obj)
        {
            return obj.ToBlob(BlobFormat.BinXml).To<T>();
        }

        /// <summary>
        /// Deserializes an object from a BLOB.
        /// </summary>
        /// <typeparam name="T">Type of the object.</typeparam>
        /// <param name="blob">The BLOB.</param>
        /// <returns>The object deserialized.</returns>
        public static T To<T>(this Blob blob)
        {
            return ReadBlob
            (
                blob,
                reader => 
                    (T)new DataContractSerializer(typeof(T))
                        .ReadObject(reader)
            );
        }

        /// <summary>
        /// Deserializes an object from a BLOB.
        /// </summary>
        /// <param name="blob">The BLOB.</param>
        /// <param name="type">Type of the object.</param>
        /// <returns>The object deserialized.</returns>
        public static object To(this Blob blob, Type type)
        {
            return ReadBlob
            (
                blob,
                reader =>
                    new DataContractSerializer(type)
                        .ReadObject(reader)
            );
        }
    }
}
